package edu.olin.scheduler.gui;

import edu.olin.scheduler.common.Category;
import edu.olin.scheduler.common.Course;
import edu.olin.scheduler.resources.IconLoader;
import java.awt.*;
import java.awt.event.*;
import java.util.EventObject;
import java.util.HashMap;
import javax.swing.*;
import javax.swing.event.CellEditorListener;
import javax.swing.tree.*;

public class CourseCatalogTree extends JTree {
    private static DefaultTreeCellRenderer dtcr = new DefaultTreeCellRenderer();
    public static final Integer STATE_SELECTED = new Integer(1);
    public static final Integer STATE_SEMISELECTED = new Integer(2);
    public static final Integer STATE_UNSELECTED = new Integer(3);
    private HashMap states = null;
    private MouseListener mouseListener = null;
    private CheckListener listener = new CheckListener();
    
    public static class CheckListener {
        /**
         * Called when a checkbox is changed
         */
        public void directChange(TreeNode node, Integer state) { }
        
        /**
         * Called when a checkbox is changed because
         * a parent item was checked / unchecked
         */
        public void indirectChangeFromBelow(TreeNode cause, TreeNode node, Integer state) { }
        
        /**
         * Called when a checkbox is changed because
         * a child item was checked / unchecked
         */
        public void indirectChangeFromAbove(TreeNode cause, TreeNode node, Integer state) { }
    };
    
    public CourseCatalogTree(TreeNode rootNode) {
        super(rootNode);
        states = new HashMap();
        mouseListener = new TreeMouseListener();
        CheckCell tr = new CheckCell();
        setCellRenderer(tr);
        setCellEditor(tr);
        setEditable(true);
        setRootVisible(true);
        setAllExpanded(true);
        setAllExpanded(false);
    }
    
    public void setCheckListener(CheckListener listener) {
        this.listener = listener;
    }
    
    public void setRootExpanded(boolean expanded){
        try{
            if(isRootVisible()){
                if(expanded){ expandRow(0); }else{ collapseRow(0); }
                }
            }catch(Exception e){}
        }
    
    public void setAllExpanded(boolean expanded){
        if(expanded){
            for(int i=0; i<getRowCount(); i++){
                expandRow(i);
                }
            }
        else{
            for(int i=getRowCount()-1; i>=0; i--){
                collapseRow(i);
                }
            }
        }
    
    /**
     * Return the selection state of a tree item
     * (either STATE_SELECTED, STATE_SEMISELECTED
     * or STATE_UNSELECTED)
     */
    public Integer getState(TreeNode child) {
        Integer state = (Integer) states.get(child);
        if (state == null)
            state = STATE_UNSELECTED;
        return state;
    }
    
    public void recursiveSetChildren(TreeNode cause, TreeNode node, boolean selected) {
        Integer state = selected ? STATE_SELECTED : STATE_UNSELECTED;
        states.put(node, state);
        listener.indirectChangeFromBelow(cause, node, state);
        for (int i=0; i<node.getChildCount(); i++) {
            TreeNode child = node.getChildAt(i);
            recursiveSetChildren(cause, child, selected);
        }
    }
    
    public void recursiveSetParents(TreeNode cause) {
        TreeNode node = cause;
        
        while (node != null) {
            Integer nodeState = STATE_UNSELECTED;
            boolean hasUnselected = node.getChildCount() == 0;
            
            for (int i=0; i<node.getChildCount(); i++) {
                TreeNode child = node.getChildAt(i);
                Integer state = getState(child);
                if (state == STATE_UNSELECTED) {
                    hasUnselected = true;
                } else if (state == STATE_SEMISELECTED) {
                    nodeState = STATE_SEMISELECTED;
                    hasUnselected = true;
                } else if (state == STATE_SELECTED) {
                    nodeState = STATE_SEMISELECTED;
                }
            }
            
            if (!hasUnselected)
                nodeState = STATE_SELECTED;
            states.put(node, nodeState);
            listener.indirectChangeFromAbove(cause, node, nodeState);
            node = node.getParent();
        }
    }
    
    /**
     * Set the selection state of a tree item. The
     * back-path is updated to reflect the new value
     */
    public void setState(TreeNode node, boolean selected) {
        Integer state = selected ? STATE_SELECTED : STATE_UNSELECTED;
        states.put(node, state);
        listener.directChange(node, state);
        
        for (int i=0; i<node.getChildCount(); i++) {
            TreeNode child = node.getChildAt(i);
            recursiveSetChildren(node, child, selected);
        }
        
        recursiveSetParents(node.getParent());
    }
    
    /**
     * Forward right click events (popup menu) to the JTree
     */
    private class TreeMouseListener extends MouseAdapter {
        public void mouseClicked(MouseEvent e) {
            if (SwingUtilities.isRightMouseButton(e)) {
                processMouseEvent(e);
            }
        }
        
        public void mouseReleased(MouseEvent e) {
            if (SwingUtilities.isRightMouseButton(e)) {
                processMouseEvent(e);
            }
        }
        
        public void mousePressed(MouseEvent e) {
            if (SwingUtilities.isRightMouseButton(e)) {
                processMouseEvent(e);
            }
        }
    };
    
    /**
     * Cell renderer / Editor implementation
     */
    private class CheckCell implements TreeCellRenderer, TreeCellEditor {
        private void setCheckState(TreeNode value, JCheckBox cb) {
            Integer state = (Integer) states.get(value);
            
            if (state == null)
                state = STATE_UNSELECTED;
            
            cb.setSelected(state == STATE_SELECTED);
            
            if(value instanceof Category && (state == STATE_SELECTED || state == STATE_SEMISELECTED))
                cb.setForeground(dtcr.getTextSelectionColor());
            else
                cb.setForeground(dtcr.getTextNonSelectionColor());
        }
        
        public Component getTreeCellComponent(final JTree tree, final Object value, boolean isSelected, boolean expanded, boolean leaf, int nRow, boolean hasFocus) {
            
            Icon icon = null;
            boolean isCourse = (value instanceof Course);
            if(value instanceof Category){
                if( ((Category)value).isCourse ){ isCourse = true; }
                if( isCourse){ icon = IconLoader.getIcon( expanded ? IconLoader.ICON_COURSE_OPEN : IconLoader.ICON_COURSE );
                    }else{ icon = expanded ? dtcr.getOpenIcon() : dtcr.getClosedIcon();
                    }
                }
            
            Color clrText, clrBackground;
            
            final JPanel panel = new JPanel();
            if(isSelected && value instanceof Category){
                clrText = dtcr.getTextSelectionColor();
                clrBackground = dtcr.getBackgroundSelectionColor();
            } else {
                clrText = dtcr.getTextNonSelectionColor();
                clrBackground = dtcr.getBackgroundNonSelectionColor();
            }
            
            //figure out the label
            String label = value.toString();
            if(value instanceof Category){ label = ((Category)value).getName(); }
            if(value instanceof Course){
                Course c = ((Course)value);
                label = c.getName();
                if(c.getCategory().isCourse){ label = "Section "+c.getSection(); }
                }
            
            JCheckBox cb = new JCheckBox(label);
            cb.setOpaque(false);
            setCheckState((TreeNode) value, cb);
            cb.addMouseListener(mouseListener);
            
            cb.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent ae) {
                    JCheckBox cb = (JCheckBox) ae.getSource();
                    setState((TreeNode) value, cb.isSelected());
                    setCheckState((TreeNode) value, cb);
                    //cb.setForeground(dtcr.getForeground());
                    tree.repaint();
                }
            });
            
            panel.setPreferredSize(new Dimension(Short.MAX_VALUE, 26));
            panel.setMinimumSize(new Dimension(20,20));
            panel.setBackground(clrBackground);
            panel.setForeground(clrText);
            panel.setOpaque(true);
            cb.setBackground(clrBackground);
            cb.setForeground(clrText);
            cb.setFocusable(false);
            cb.setOpaque(true);
            panel.setLayout(new BorderLayout(0,0));
            panel.setOpaque(true);
            if(!(value instanceof Course)){
                JLabel jl = new JLabel(label,icon,JLabel.LEFT);
                jl.setForeground(clrText);
                cb.setVisible(false); cb.setEnabled(false); panel.add(jl,BorderLayout.WEST); }
            else if(icon!=null){
                JLabel jl = new JLabel(icon);
                jl.setForeground(clrText);
                panel.add(jl,BorderLayout.WEST);
                }
            cb.setIcon(IconLoader.getIcon(IconLoader.ICON_COURSE));
            cb.setSelectedIcon(IconLoader.getIcon(IconLoader.ICON_COURSE_SELECTED));
            cb.setIconTextGap(4);
            Insets s = cb.getMargin();
            cb.setMargin(new Insets(s.top,0,s.bottom,s.right));
            panel.add(cb,BorderLayout.CENTER);
            return panel;
        }
        
        public Component getTreeCellRendererComponent(JTree tree,
                Object objValue, boolean isSelected, boolean isExpanded,
                boolean inLeaf, int nRow, boolean hasFocus) {
            return getTreeCellComponent(tree, objValue, isSelected, isExpanded,
                    inLeaf, nRow, hasFocus);
        }
        
        public Component getTreeCellEditorComponent(JTree tree,
                Object objValue, boolean isSelected, boolean isExpanded,
                boolean inLeaf, int nRow) {
            return getTreeCellComponent(tree, objValue, true, isExpanded,
                    inLeaf, nRow, true);
        }
        
        public void addCellEditorListener(CellEditorListener l) {
        }
        
        public void cancelCellEditing() {
        }
        
        public Object getCellEditorValue() {
            return this;
        }
        
        public boolean isCellEditable(EventObject evt) {
            if (evt instanceof MouseEvent) {
                MouseEvent mevt = (MouseEvent) evt;
                if (mevt.getClickCount() == 1) {
                    return true;
                }
            }
            return false;
        }
        
        public void removeCellEditorListener(CellEditorListener l) {
        }
        
        public boolean shouldSelectCell(EventObject anEvent) {
            return true;
        }
        
        public boolean stopCellEditing() {
            return false;
        }
    }
}